<template>
  <div class="file-picker-main">
    <div class="file-picker-body">
      <div class="file-picker-toolbar">
        <ElUpload
          action=""
          :accept="accept"
          :showFileList="false"
          :beforeUpload="handleUpload"
        >
          <ElButton type="primary" class="ele-btn-icon" :icon="UploadOutlined">
            上传
          </ElButton>
        </ElUpload>
        <div class="file-picker-search">
          <ElInput
            :clearable="true"
            v-model="searchKeyword"
            placeholder="请输入文件名"
            @clear="handleSearch"
            @change="handleSearch"
          />
          <ElButton type="primary" @click="handleSearch">搜索</ElButton>
        </div>
        <EleSegmented
          v-model="isGridMode"
          :items="modeSegmentedItems"
          :validateEvent="false"
        />
      </div>
      <template v-if="fileData.length">
        <div class="file-picker-file-list" @scroll="handleFileListScroll">
          <EleFileList
            :boxChoose="true"
            :icons="localIcons"
            :smallIcons="localSmallIcons"
            :contextMenuProps="contextMenuProps"
            v-bind="fileListProps || {}"
            :data="fileData"
            :columns="columns"
            :grid="isGridMode === 1"
            :selectionType="limit === 1 ? 'radio' : 'checkbox'"
            :selections="fileSelections"
            v-model:current="fileCurrent"
            :contextMenus="fileContextMenus"
            :class="[{ 'is-ping-top': isPingTop }]"
            @itemClick="handleFileItemClick"
            @itemContextMenu="handleFileCtxMenuClick"
            @update:selections="updateSelections"
            @itemContextOpen="handleFileCtxMenuOpen"
          >
            <!-- 头像列 -->
            <template #avatar="{ item }">
              <el-avatar
                v-if="item.manager.avatar"
                :src="item.manager.avatar"
                :size="32"
                @click.stop=""
                style="vertical-align: -16px"
              />
              <el-avatar
                v-else
                :size="32"
                style="
                  background: var(--el-color-primary);
                  vertical-align: -2px;
                "
                @click.stop=""
              >
                {{
                  item.manager.nickname && item.manager.nickname.length > 2
                    ? item.manager.nickname.substring(
                        item.manager.nickname.length - 2,
                      )
                    : item.manager.nickname
                }}
              </el-avatar>
              {{ item.manager.nickname }}
            </template>
          </EleFileList>
        </div>
        <ElePagination
          size="small"
          :teleported="false"
          :pageSizes="[18, 24, 30, 36, 42]"
          layout="total,prev,pager,next,sizes"
          v-bind="paginationProps || {}"
          :currentPage="currentPage"
          :pageSize="pageSize"
          :total="total"
          @update:currentPage="handleCurrentPageChange"
          @update:pageSize="handlePageSizeChange"
        />
      </template>
      <ElEmpty
        v-else
        :imageSize="80"
        description="无数据"
        v-bind="emptyProps || {}"
      />
    </div>
    <FileSelections
      ref="fileSelectionsRef"
      :fileSelections="
        limit === 1 ? (fileCurrent ? [fileCurrent] : []) : fileSelections
      "
      :limit="limit"
      :selectionListProps="selectionListProps"
      :baseIndex="baseIndex"
      @clearSelections="clearSelections"
      @removeItem="removeItem"
    />
  </div>
</template>

<script setup>
import { ref, reactive, watch, markRaw } from "vue";
import {
  localIcons,
  localSmallIcons,
} from "ele-admin-plus/es/ele-file-list/icons";
import {
  UploadOutlined,
  MenuOutlined,
  AppstoreOutlined,
  EditOutlined,
  DragOutlined,
  DeleteOutlined,
} from "@/components/icons";
import { uploadFile } from "@/api/system/file";
import { pageFiles, addFile } from "@/api/system/file";
import FileSelections from "./file-selections.vue";

const props = defineProps({
  /** 最大选择数量 */
  limit: Number,
  /** 文件大小限制, 单位MB */
  fileLimit: Number,
  /** 接受上传的文件类型 */
  accept: String,
  /** 接口查询参数 */
  params: Object,
  /** 文件列表自定义属性 */
  fileListProps: Object,
  /** 已选列表自定义属性 */
  selectionListProps: Object,
  /** 空组件属性 */
  emptyProps: Object,
  /** 分页组件属性 */
  paginationProps: Object,
  /** 统一设置层级 */
  baseIndex: Number,
  /** 消息提示组件 */
  messageIns: [Object, Function],
});

const emit = defineEmits([
  "queryStart",
  "queryDone",
  "renameFile",
  "moveFile",
  "removeFile",
  "fileItemContextOpen",
]);

/** 当前分组id */
const fileParentId = ref();

/** 文件数据 */
const fileData = ref([]);

/** 选中的文件数据 */
const fileSelections = ref([]);

/** 单选选中的文件数据 */
const fileCurrent = ref();

/** 是否网格模式 */
const isGridMode = ref(1);

/** 搜索关键字 */
const searchKeyword = ref("");

/** 当前页码 */
const currentPage = ref(1);

/** 每页显示数量 */
const pageSize = ref(24);

/** 总数量 */
const total = ref(0);

/** 文件列表是否固定表头 */
const isPingTop = ref(false);

/** 视图模式分段器数据 */
const modeSegmentedItems = [
  { icon: MenuOutlined, value: 0, iconStyle: { transform: "scale(0.9)" } },
  { icon: AppstoreOutlined, value: 1 },
];

/** 已选择列表组件 */
const fileSelectionsRef = ref();

/** 文件列表右键菜单属性 */
const contextMenuProps = reactive({
  menuStyle: { minWidth: "120px" },
  iconProps: { size: 15 },
  popperOptions: { strategy: "fixed" },
  zIndex: props.baseIndex,
});

/** 校验最大选择数量 */
const checkLimit = (selections, isAdd) => {
  if (
    props.limit &&
    props.limit > 1 &&
    (isAdd ? selections.length >= props.limit : selections.length > props.limit)
  ) {
    props.messageIns?.error?.(`最多只能选择 ${props.limit} 个`);
    return false;
  }
};

/** 更新选中数据 */
const updateSelections = (selections) => {
  if (checkLimit(selections) !== false) {
    fileSelections.value = selections;
    return;
  }
  if (props.limit && props.limit > 1) {
    fileSelections.value = selections.slice(0, props.limit);
  }
};

/** 清空选中 */
const clearSelections = () => {
  fileSelections.value = [];
  fileCurrent.value = void 0;
};

/** 获取选中数据 */
const getSelections = () => {
  if (fileSelectionsRef.value) {
    const selections = fileSelectionsRef.value.getSelections() || [];
    return selections.map((d) => d.file);
  }
  return [];
};

/** 移除选中 */
const removeItem = (item) => {
  if (props.limit === 1) {
    clearSelections();
    return;
  }
  const index = fileSelections.value.findIndex((d) => d.key === item.key);
  if (index !== -1) {
    fileSelections.value.splice(index, 1);
  }
};

/** 搜索 */
const handleSearch = () => {
  queryData({ page: 1 });
};

/** 切换当前页码 */
const handleCurrentPageChange = (page) => {
  if (currentPage.value !== page) {
    currentPage.value = page;
    queryData();
  }
};

/** 切换每页显示数量 */
const handlePageSizeChange = (limit) => {
  if (pageSize.value !== limit) {
    currentPage.value = 1;
    pageSize.value = limit;
    const maxPage = Math.ceil(total.value / limit);
    if (maxPage && currentPage.value > maxPage) {
      currentPage.value = maxPage;
    }
    queryData();
  }
};

/** 文件列表点击事件 */
const handleFileItemClick = (item) => {
  if (props.limit === 1) {
    fileCurrent.value = item;
    return;
  }
  const index = fileSelections.value.findIndex((d) => d.key === item.key);
  if (index !== -1) {
    fileSelections.value.splice(index, 1);
    return;
  }
  if (checkLimit(fileSelections.value, true) !== false) {
    fileSelections.value.push(item);
  }
};

/** 文件列表右键菜单点击事件 */
const handleFileCtxMenuClick = (option) => {
  const { key, item } = option;
  const fileItem = item.file;
  if (key === "preview") {
    if (!item.thumbnail) {
      window.open(item.url);
      return;
    }
    const data = fileData.value.filter((d) => !!d.thumbnail);
    const urls = data.map((d) => d.url);
    const index = data.indexOf(item);
    if (index !== -1) {
      fileSelectionsRef.value &&
        fileSelectionsRef.value.openImagePreview(urls, index);
    }
  } else if (key === "rename") {
    emit("renameFile", fileItem, true);
  } else if (key === "move") {
    emit("moveFile", fileItem, true);
  } else if (key === "remove") {
    emit("removeFile", fileItem, true);
  }
};

/** 文件列表右键菜单数据 */
const fileContextMenus = (item) => {
  const menus = [
    {
      title: "重命名",
      command: "rename",
      icon: markRaw(EditOutlined),
    },
    {
      title: "移动到",
      command: "move",
      icon: markRaw(DragOutlined),
    },
    {
      title: "删除",
      command: "remove",
      icon: markRaw(DeleteOutlined),
      divided: true,
      danger: true,
    },
  ];
  if (item.thumbnail) {
    menus[0].divided = true;
    menus.unshift({ title: "预览", command: "preview" });
  } else {
    menus[0].divided = true;
    menus.unshift({ title: "打开", command: "preview" });
  }
  return menus;
};

/** 文件列表右键菜单打开事件 */
const handleFileCtxMenuOpen = () => {
  emit("fileItemContextOpen");
};

/** 校验选择的文件 */
const checkFile = (file) => {
  if (!file) {
    return;
  }
  if (props.accept === "image/*") {
    if (!file.type.startsWith("image")) {
      props.messageIns?.error?.("只能选择图片");
      return;
    }
  } else if (props.accept === ".xls,.xlsx") {
    if (
      ![
        "application/vnd.ms-excel",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
      ].includes(file.type)
    ) {
      props.messageIns?.error?.("只能选择 excel 文件");
      return;
    }
  }
  if (props.fileLimit && file.size / 1024 / 1024 > props.fileLimit) {
    props.messageIns?.error?.(`大小不能超过 ${props.fileLimit}MB`);
    return;
  }
  return true;
};

/** 文件上传事件 */
const handleUpload = (file) => {
  if (checkFile(file)) {
    const loading = props.messageIns?.loading?.({
      message: "上传中..",
      plain: true,
      mask: true,
    });
    uploadFile(file)
      .then((data) => {
        addFile({
          name: data.original_name,
          is_directory: 0,
          parent_id: fileParentId.value,
          path: data.url,
          length: data.file_size,
          content_type: data.content_type,
        })
          .then(() => {
            loading?.close?.();
            props.messageIns?.success?.("上传成功");
            queryData();
          })
          .catch((e) => {
            loading?.close?.();
            props.messageIns?.error?.(e.message);
          });
      })
      .catch((e) => {
        loading?.close?.();
        props.messageIns?.error?.(e.message);
      });
  }
  return false;
};

/** 文件列表滚动事件 */
const handleFileListScroll = (e) => {
  const wrapEl = e.currentTarget;
  const scrollTop = wrapEl.scrollTop;
  isPingTop.value = scrollTop > 1;
};

/** 触发文件数据请求完成 */
const handleQueryDone = () => {
  emit("queryDone");
};

const columns = ref([
  {
    title: "文件大小",
    prop: "length",
    style: { width: "120px", flexShrink: 0 },
  },
  {
    title: "上传人",
    prop: "avatar",
    slot: "avatar",
    style: { width: "120px", flexShrink: 0 },
  },

  {
    title: "上传时间",
    prop: "updateTime",
    style: { width: "180px", flexShrink: 0 },
  },
]);

/** 判断是否是图片文件 */
const isImageFile = (item) => {
  return (
    typeof item.content_type === "string" &&
    item.content_type.startsWith("image/") &&
    item.url
  );
};

/** 格式化文件大小 */
const formatLength = (length) => {
  if (length == null) {
    return "-";
  }
  if (length < 1024) {
    return length + "B";
  } else if (length < 1024 * 1024) {
    return (length / 1024).toFixed(1) + "KB";
  } else if (length < 1024 * 1024 * 1024) {
    return (length / 1024 / 1024).toFixed(1) + "M";
  } else {
    return (length / 1024 / 1024 / 1024).toFixed(1) + "G";
  }
};

/** 查询文件数据 */
const queryData = (params) => {
  emit("queryStart");
  if (params) {
    if (params.page != null) {
      currentPage.value = params.page;
    }
    if (params.parent_id != null) {
      fileParentId.value = params.parent_id;
    }
    if (params.name != null) {
      searchKeyword.value = params.name;
    }
  }
  const parent_id = fileParentId.value;
  pageFiles({
    page: currentPage.value,
    limit: pageSize.value,
    parent_id: parent_id === -1 ? void 0 : parent_id,
    name: searchKeyword.value,
    is_directory: 0,
    ...(props.params || {}),
  })
    .then((result) => {
      if (!result?.data?.length && result?.count) {
        const maxPage = Math.ceil(result.count / pageSize.value);
        if (maxPage && currentPage.value > maxPage) {
          queryData({ page: maxPage });
          return;
        }
      }
      total.value = result?.count || 0;
      fileData.value =
        result?.data?.map?.((d) => {
          return {
            key: d.id,
            name: d.name,
            url: d.url,
            thumbnail: isImageFile(d) ? d.thumbnail : void 0,
            length: formatLength(d.length),
            updateTime: d.updated_at,
            is_directory: d.is_directory === 1,
            file: d,
            manager: d.manager,
          };
        }) || [];
      handleQueryDone();
    })
    .catch((e) => {
      fileData.value = [];
      props.messageIns?.error?.(e.message);
      handleQueryDone();
    });
};

watch(
  () => props.limit,
  () => {
    clearSelections();
  },
);

watch(
  () => props.baseIndex,
  (baseIndex) => {
    contextMenuProps.zIndex = baseIndex;
  },
);

defineExpose({ queryData, clearSelections, getSelections });
</script>
